package com.sensoro.beacon.kit;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import com.sensoro.beacon.kit.AnalyseFactory.GattInfo;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Handler;
import android.util.Log;

public class SensoroBeaconFlowConnection {
	static final int MSG_WRITE = 1000;
	/**
	 * Success.
	 */
	public static final int SUCCESS = 0;
	/**
	 * Failure.
	 */
	public static final int FAILURE = 1;
	/**
	 * Failure.
	 */
	public static final int VER_ILLEGAL = 2;
	/**
	 * Beacon model error.
	 */
	public static final int ERROR_BEACON_MODEL = 6;
	/**
	 * Disconnected from remote device.
	 */
	private static final int DISCONNECTED_BY_REMOTE_DEVICE = 9;

	private static final String TAG = SensoroBeaconConnection.class.getSimpleName();

	public static final int CONNECTED_TIME_OUT_FLAGE = 5;
	private static final int CONNECTE_TIME_OUT = 20 * 1000;

	private BluetoothGattService cmdService = null;
	private BluetoothGattCharacteristic cmdWriteChar = null;
	private BluetoothGattCharacteristic cmdReadChar = null;
	private BluetoothGatt bluetoothGatt = null;
	private BluetoothManager bluetoothManager = null;
	private BluetoothAdapter bluetoothAdapter = null;
	private String bluetoothDeviceAddress = null;

	private Context context;
	private Beacon beacon;
	private BeaconFlowConnectionCallback callback;

	private boolean isConnected;
	private Handler handler;

	private TimeOutRunnable timeOutRunnable;
	private ConnectTimeOutRunnable connectTimeOutRunnable;
	private String beaconType; // beacon 型号
	private String beaconFirmware; // beacon 固件版本

	// private HashMap<String, WriteCallback> writeCallbackHashMap;
	private ConnectCallback connectCallback;
	private BeaconFlowSettingCallback settingCallback;
	private BeaconConfiguration settingConfiguration;

	// private IAnalytical analyticalUtils;
	ArrayList<byte[]> writeDataPackages;
	int currentPackageNo = 0;

	IAnalyse analyse;

	public SensoroBeaconFlowConnection(Context context, Beacon beacon, BeaconFlowConnectionCallback callback) throws SensoroException {
		if (context == null) {
			throw new SensoroException("Context is null");
		}
		if (beacon == null) {
			throw new SensoroException("Beacon is null");
		}
		if (callback == null) {
			throw new SensoroException("BeaconConnectionCallback is null");
		}
		this.callback = callback;
		this.context = context;
		this.beacon = beacon;

		handler = new Handler();
		timeOutRunnable = new TimeOutRunnable();
		connectTimeOutRunnable = new ConnectTimeOutRunnable();

		beaconType = beacon.getHardwareModelName();
		beaconFirmware = beacon.getFirmwareVersion();
		// writeCallbackHashMap = new HashMap<String, WriteCallback>();
		connectCallback = new ConnectCallback() {
			@Override
			public void onConnectedState(int newState, int status) {

			}
		};
	}

	/**
	 * Connect the beacon.The callback
	 * {@link com.sensoro.beacon.kit.SensoroBeaconConnection.BeaconConnectionCallback#onConnectedState(Beacon, int, int)}
	 * will be called.
	 */
	public void connect() {
		// beacon 型号不是 A0,B0,C0,回调错误
		if (!isA0Beacon() && !isB0Beacon() && !isC0Beacon()) {
			callback.onConnectedState(beacon, Beacon.CONNECTED, ERROR_BEACON_MODEL);
			return;
		}
		if (!initialize()) {
			callback.onConnectedState(beacon, Beacon.CONNECTED, FAILURE);
			if (SensoroBeaconManager.DEBUG) {
				Log.d(TAG, "connect---callback connect failure:bleHelper init failure");
			}
			return;
		}
		if (!connectBeacon(beacon.getMacAddress(), bluetoothGattCallback)) {
			callback.onConnectedState(beacon, Beacon.CONNECTED, FAILURE);
			if (SensoroBeaconManager.DEBUG) {
				Log.d(TAG, "connect---callback connect failure:bleHelper connect failure");
			}
		}
		// timeOut 时间之后判断是否超时
		handler.postDelayed(timeOutRunnable, CONNECTE_TIME_OUT);
		if (SensoroBeaconManager.DEBUG) {
			Log.d(TAG, "connect timeout---start time out runnable");
		}
	}

	/**
	 * Initializes a reference to the local Bluetooth adapter.
	 * 
	 * @return Return true if the initialization is successful.
	 */
	public boolean initialize() {
		// For API level 18 and above, get a reference to BluetoothAdapter
		// through
		// BluetoothManager.
		if (bluetoothManager == null) {
			bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
			if (bluetoothManager == null) {
				// Log.e(TAG, "Unable to initialize BluetoothManager.");
				return false;
			}
		}

		bluetoothAdapter = bluetoothManager.getAdapter();
		if (bluetoothAdapter == null) {
			// Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
			return false;
		}
		return true;
	}

	/**
	 * Connects to the GATT server hosted on the Bluetooth LE device.
	 * 
	 * @param address
	 *            The device address of the destination device.
	 * @return Return true if the connection is initiated successfully. The
	 *         connection result is reported asynchronously through the
	 *         {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
	 *         callback.
	 */
	public boolean connectBeacon(final String address, BluetoothGattCallback gattCallback) {
		if (bluetoothAdapter == null || address == null) {
			// Log.w(TAG,
			// "BluetoothAdapter not initialized or unspecified address.");
			return false;
		}

		// Previously connected device. Try to reconnect.
		if (bluetoothDeviceAddress != null && address.equals(bluetoothDeviceAddress) && bluetoothGatt != null) {
			// Log.d(TAG,
			// "Trying to use an existing mBluetoothGatt for connection.");
			return bluetoothGatt.connect();
		}

		final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
		// We want to directly connect to the device, so we are setting the
		// autoConnect
		// parameter to false.
		bluetoothDeviceAddress = address;
		bluetoothGatt = device.connectGatt(context, false, gattCallback);
		// Log.d(TAG, "Trying to create a new connection.");
		System.out.println("device.getBondState==" + device.getBondState());
		return true;
	}

	/**
	 * Disconnect the beacon.
	 */
	public void disconnect() {
		if (close()) {
			callback.onConnectedState(beacon, Beacon.DISCONNECTED, SUCCESS);
			connectCallback.onConnectedState(ConnectCallback.STATE_DISCONNECTED, ConnectCallback.SUCCESS);
			isConnected = false;
		} else {
			callback.onConnectedState(beacon, Beacon.DISCONNECTED, FAILURE);
			connectCallback.onConnectedState(ConnectCallback.STATE_DISCONNECTED, ConnectCallback.FAILURE);
		}
	}

	/**
	 * 
	 * @param config
	 * @param callback
	 */
	public void setBeaconConfiguration(BeaconConfiguration config, BeaconFlowSettingCallback callback) {
		if (config != null && callback != null) {
			settingConfiguration = config;
			settingCallback = callback;
			analyse = AnalyseFactory.creatAnalyse(AnalyseFactory.protocolVersionLatest, beacon.getHardwareModelName(), beacon.firmwareVersion);
			writeDataPackages = analyse.combineDataPackages(config);
			cmdWriteChar.setValue(writeDataPackages.get(currentPackageNo));
			bluetoothGatt.writeCharacteristic(cmdWriteChar);
		}
	}

	private boolean isA0Beacon() {
		return beaconType.equals(Beacon.HV_A0);
	}

	private boolean isB0Beacon() {
		return beaconType.equals(Beacon.HV_B0);
	}

	private boolean isC0Beacon() {
		return beaconType.equals(Beacon.HV_C0);
	}

	private BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
		/**
		 * BLE 连接状态回调
		 */
		@Override
		public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
			// 连接状态回调，停止此次连接超时计时
			handler.removeCallbacks(timeOutRunnable);
			handler.removeCallbacks(connectTimeOutRunnable);
			if (SensoroBeaconManager.DEBUG) {
				Log.d(TAG, "connect timeout---stop time out runnable");
			}

			if (status == BluetoothGatt.GATT_SUCCESS) {
				if (newState == BluetoothProfile.STATE_CONNECTED) {
					// 连接 beacon 成功,获取 beacon 中的服务
					gatt.discoverServices();
				}
				if (newState == BluetoothProfile.STATE_DISCONNECTED) {
					isConnected = false;
					callback.onConnectedState(beacon, Beacon.DISCONNECTED, SUCCESS);
					connectCallback.onConnectedState(ConnectCallback.STATE_DISCONNECTED, ConnectCallback.SUCCESS);
					if (SensoroBeaconManager.DEBUG) {
						Log.d(TAG, "onConnectionStateChange---callback disconnect success");
					}
				}
			} else if (status == BluetoothGatt.GATT_FAILURE) {
				isConnected = false;
				if (newState == BluetoothProfile.STATE_CONNECTED) {
					callback.onConnectedState(beacon, Beacon.CONNECTED, FAILURE);
					connectCallback.onConnectedState(ConnectCallback.STATE_CONNECTED, ConnectCallback.FAILURE);
					if (SensoroBeaconManager.DEBUG) {
						Log.d(TAG, "onConnectionStateChange---callback connect failure");
					}
				}
				if (newState == BluetoothProfile.STATE_DISCONNECTED) {
					callback.onConnectedState(beacon, Beacon.DISCONNECTED, FAILURE);
					connectCallback.onConnectedState(ConnectCallback.STATE_DISCONNECTED, ConnectCallback.FAILURE);
					if (SensoroBeaconManager.DEBUG) {
						Log.d(TAG, "onConnectionStateChange---callback disconnect failure");
					}
				}
			} else if (status == 133) {
				isConnected = false;
				callback.onConnectedState(beacon, Beacon.CONNECTED, SensoroBeaconConnection.FAILURE);
				connectCallback.onConnectedState(ConnectCallback.STATE_CONNECTED, ConnectCallback.FAILURE);
			} else {
				isConnected = false;
				if (newState == BluetoothProfile.STATE_CONNECTED) {
					callback.onConnectedState(beacon, Beacon.CONNECTED, status);
					connectCallback.onConnectedState(ConnectCallback.STATE_CONNECTED, ConnectCallback.FAILURE);
					if (SensoroBeaconManager.DEBUG) {
						Log.d(TAG, "onConnectionStateChange---callback connect failure");
					}
				}
				if (newState == BluetoothProfile.STATE_DISCONNECTED) {
					callback.onConnectedState(beacon, Beacon.DISCONNECTED, DISCONNECTED_BY_REMOTE_DEVICE);
					connectCallback.onConnectedState(ConnectCallback.STATE_DISCONNECTED, ConnectCallback.FAILURE);
					if (SensoroBeaconManager.DEBUG) {
						Log.d(TAG, "onConnectionStateChange---callback disconnect failure");
					}
				}
			}
			super.onConnectionStateChange(gatt, status, newState);
		}

		/**
		 * BLE 搜索服务回调
		 * 
		 * @param gatt
		 * @param status
		 */
		@Override
		public void onServicesDiscovered(BluetoothGatt gatt, int status) {
			// 获取服务成功,读取 Base 和 Sensor 的设置
			if (status == BluetoothGatt.GATT_SUCCESS) {
				List<BluetoothGattService> gattServiceList = gatt.getServices();
				if (checkGattServices(beaconFirmware, beaconType, gattServiceList)) {
					if (!getFlowCharacteristic()) {
						// 没有找到 BaseSettings 的特征值
						callback.onConnectedState(beacon, Beacon.CONNECTED, FAILURE);
						connectCallback.onConnectedState(ConnectCallback.STATE_CONNECTED, ConnectCallback.FAILURE);
						gatt.close();
						if (SensoroBeaconManager.DEBUG) {
							Log.d(TAG, "onServicesDiscovered---callback connect failure:no baseSetting char");
						}
					}
				} else {
					// 没有找到 BaseSettings 或者 SensoSettings 服务
					callback.onConnectedState(beacon, Beacon.CONNECTED, FAILURE);
					connectCallback.onConnectedState(ConnectCallback.STATE_CONNECTED, ConnectCallback.FAILURE);
					gatt.close();
					if (SensoroBeaconManager.DEBUG) {
						Log.d(TAG, "onServicesDiscovered---callback connect failure:no baseSetting or sensorSetting service");
					}
				}
			} else {
				// 读取 Beacon 服务失败,断开连接
				callback.onConnectedState(beacon, Beacon.CONNECTED, FAILURE);
				connectCallback.onConnectedState(ConnectCallback.STATE_CONNECTED, ConnectCallback.FAILURE);
				gatt.close();
				if (SensoroBeaconManager.DEBUG) {
					Log.d(TAG, "onServicesDiscovered---callback connect failure:get beacon service failure");
				}
			}
			super.onServicesDiscovered(gatt, status);
		}

		// 校验beacon是否包含对应的服务
		public boolean checkGattServices(String firmware, String model, List<BluetoothGattService> gattServiceList) {
			for (BluetoothGattService bluetoothGattService : gattServiceList) {
				if (bluetoothGattService.getUuid().equals(GattInfo.CMD_SERVICE_UUID)) {
					cmdService = bluetoothGattService;
				}
			}
			if (cmdService != null) {
				return true;
			}
			return false;
		}

		boolean getFlowCharacteristic() {
			if (cmdService != null) {
				cmdReadChar = cmdService.getCharacteristic(GattInfo.CMD_READ_UUID);
				cmdWriteChar = cmdService.getCharacteristic(GattInfo.CMD_WRITE_UUID);
				if (cmdReadChar != null) {
					bluetoothGatt.setCharacteristicNotification(cmdReadChar, true);
					BluetoothGattDescriptor temperatureDescriptor = cmdReadChar.getDescriptor(GattInfo.CLIENT_CHARACTERISTIC_CONFIG);
					temperatureDescriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
					bluetoothGatt.writeDescriptor(temperatureDescriptor);
				}
				return true;
			} else {
				return false;
			}
		}

		@Override
		public void onCharacteristicChanged(BluetoothGatt gatt, android.bluetooth.BluetoothGattCharacteristic characteristic) {
			UUID uuid = characteristic.getUuid();
			byte[] data = characteristic.getValue();
			if (uuid.equals(GattInfo.CMD_READ_UUID)) {
				if (data[4] == AnalyseFactory.ERROR_CODE_SUCCESS) {
					// errcode is suc.
					analyse = AnalyseFactory.creatAnalyse(data[0], beacon.getHardwareModelName(), beacon.getFirmwareVersion());
					BeaconConfiguration beaconConfiguration = null;
					if (analyse != null) {
						// suc
						beaconConfiguration = analyse.analysisCmd(data);
						if (beaconConfiguration == null) {
							// can not get configuration.
							callback.onConnectedState(beacon, Beacon.CONNECTED, VER_ILLEGAL);
						}
						boolean status = configurationToBeacon(beaconConfiguration);
						if (!status) {
							// to beacon failure.
							callback.onConnectedState(beacon, Beacon.CONNECTED, VER_ILLEGAL);
						}
						if (beacon != null) {
							callback.onConnectedState(beacon, Beacon.CONNECTED, SUCCESS);
						}
					} else {
						// hw or fw not surpport.
						callback.onConnectedState(beacon, Beacon.CONNECTED, VER_ILLEGAL);
					}
				} else {
					// errcode is not suc.
					callback.onConnectedState(beacon, Beacon.CONNECTED, data[4]);
				}
			} else if (uuid.equals(GattInfo.CMD_WRITE_UUID)) {
				currentPackageNo = 0;
				if (data[4] != AnalyseFactory.ERROR_CODE_SUCCESS) {
					// write failure can callback.
					settingCallback.onSettingState(beacon, (int) data[4]);
				} else {
					// write suc can callback.
					configurationToBeacon(settingConfiguration);
					settingCallback.onSettingState(beacon, (int) data[4]);
				}
			}
		}

		private boolean configurationToBeacon(BeaconConfiguration beaconConfiguration) {
			if (beaconConfiguration == null) {
				return false;
			}
			beacon.major = beaconConfiguration.major;
			beacon.minor = beaconConfiguration.minor;
			beacon.proximityUUID = beaconConfiguration.uuid;
			return true;
		}

		@Override
		public void onCharacteristicRead(BluetoothGatt gatt, android.bluetooth.BluetoothGattCharacteristic characteristic, int status) {
		}

		@Override
		public void onCharacteristicWrite(BluetoothGatt gatt, android.bluetooth.BluetoothGattCharacteristic characteristic, int status) {
			// TODO
			UUID uuid = characteristic.getUuid();
			if (uuid.equals(GattInfo.CMD_WRITE_UUID)) {
				currentPackageNo++;
				cmdWriteChar.setValue(writeDataPackages.get(currentPackageNo));
				bluetoothGatt.writeCharacteristic(cmdWriteChar);
			}
		}

		@Override
		public void onDescriptorWrite(BluetoothGatt gatt, android.bluetooth.BluetoothGattDescriptor descriptor, int status) {
			if (status == BluetoothGatt.GATT_SUCCESS && descriptor.getUuid().equals(GattInfo.CLIENT_CHARACTERISTIC_CONFIG)) {
				analyse = AnalyseFactory.creatAnalyse(AnalyseFactory.protocolVersionLatest, beacon.getHardwareModelName(), beacon.firmwareVersion);
				byte[] readStampByte = analyse.combineReadCmd();
				if (cmdWriteChar != null) {
					cmdWriteChar.setValue(readStampByte);
					bluetoothGatt.writeCharacteristic(cmdWriteChar);
				}
			}
		}
	};

	/**
	 * Close the connection of the beacon.
	 */
	private boolean close() {
		if (bluetoothGatt != null) {
			bluetoothGatt.close();
			bluetoothGatt = null;
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Return the status whether the beacon is connected or not.
	 */
	public boolean isConnected() {
		return isConnected;
	}

	class TimeOutRunnable implements Runnable {
		@Override
		public void run() {
			if (!isConnected) {
				close();
				callback.onConnectedState(beacon, Beacon.CONNECTED, CONNECTED_TIME_OUT_FLAGE);
				if (SensoroBeaconManager.DEBUG) {
					Log.d(TAG, "TimeOutRunnable---callback connect failure:connect time out");
				}
			}
		}
	}

	class ConnectTimeOutRunnable implements Runnable {
		@Override
		public void run() {
			if (!isConnected) {
				close();
				connectCallback.onConnectedState(ConnectCallback.STATE_CONNECTED, ConnectCallback.CONNECTED_TIME_OUT);
				if (SensoroBeaconManager.DEBUG) {
					Log.d(TAG, "ConnectTimeOutRunnable---callback connect failure:connect time out");
				}
			}
		}
	}

	/**
	 * The exception of the SDK.
	 */
	public class SensoroException extends Exception {
		private static final long serialVersionUID = 5433959851204242014L;

		public SensoroException() {
		}

		public SensoroException(String message) {
			super(message);
		}
	}

	public abstract interface BeaconFlowConnectionCallback {

		/**
		 * The callback method when the state of the connection is changed.
		 * 
		 * @param beacon
		 *            The current beacon.
		 * @param newState
		 *            The new connection state of connecting the beacon. It can
		 *            be one of CONNECTED or DISCONNECTED. See
		 *            {@link Beacon#CONNECTED} and {@link Beacon#DISCONNECTED}
		 * @param status
		 *            The result of connecting the beacon.It can be SUCCESS or
		 *            FAILURE. See
		 *            {@link com.sensoro.beacon.kit.SensoroBeaconConnection#SUCCESS}
		 *            and
		 *            {@link com.sensoro.beacon.kit.SensoroBeaconConnection#FAILURE}
		 */
		public void onConnectedState(Beacon beacon, int newState, int status);
	}

	public abstract interface BeaconFlowSettingCallback {

		/**
		 * The callback method when the state of the connection is changed.
		 * 
		 * @param beacon
		 *            The current beacon.
		 * @param errotCode
		 *            ErrorCode
		 */
		public void onSettingState(Beacon beacon, int errorCode);
	}

}
